package sfsoftware.game.trevor;

import sfsoftware.game.TrevorLegacy;
import sfsoftware.game.actor.attributes.*;
import sfsoftware.game.actor.item.Bomb;
import sfsoftware.game.actor.item.Item;
import sfsoftware.game.trevor.ui.Dialog;
import sfsoftware.util.*;

public class Player extends Actor implements Permanent, Updatable, Poisonable, Explodable 
{			
	private final int MAX_FRAMES = 4;
	
	public static boolean keyDown = false;	
	private static Player playerInstance = null;
	
	private boolean invulnerable = false;
	private boolean invulnerabilityDraw = false;		
	private boolean poisoned = false;
	
	// If noClip is set to false the player is blocked by unwalkable tiles
	private boolean noClip = false;
	
	private int health = 100;
	private int maxHealth = 100;	
	private int money = 0;
	private int walletSize = 100;

	private float wiffaDamage = 1;
	private float damageResistance = 1;
	private float luck = 1;
	private float spirit = 1;	// Determines how many charges per attack!
	private int spiritAttackLevel = 1;
	
	private float x = 0;
	private float y = 0;

	public Inventory<Item> inventory = new Inventory<Item>();	
	public Item currentItem = null;
	
	private Clock animationTimer = null;	
	private Clock invulnerabilityTimer = new Clock(500);
	private int invulnerabilityCoolDown = 0;
	
	private boolean usingWiffa = false;
	private float wiffaAccel = 3.25f;
	
	public Player(int destX, int destY, String texture, int destWidth, int destHeight) 
	{
		super(destX, destY, texture, destWidth, destHeight);		
		setSpeed(2);		
		playerInstance = this;
	}	
	
	public void accelerate()
	{
		//System.out.println(this.toString() + " accelerating at " + Math.round(getAcceleration()));
		
		if(!(super.move(xDirection * getAcceleration(), yDirection * getAcceleration(), TrevorLegacy.tileMap)))
		{
			if(isUsingWiffa())
			{
				// Ting! We hit an unwalkable thing while swinging our wiffa!
			}
		}		
		
		if(Math.round(getAcceleration()) > 0) 
		{
			setAcceleration(getAcceleration() - friction);
		}
		else if(Math.round(getAcceleration()) < 0) 
		{
			setAcceleration(getAcceleration() + friction);
		}	
		else if(Math.round(getAcceleration()) == 0)
		{
			setAcceleration(0);
		}
		
	}
	
	public static void addToInventory(Item i)
	{
		getPlayer().inventory.add(i);
	}
	
	public static boolean addToRuples(int ruples)
	{
		// Only add money if their wallet can hold
		if(ruples < getPlayer().walletSize)
		{
			getPlayer().money = getPlayer().money + ruples;
			return true;
		}
		else
		{
			return false;
		}
	}
	
	@Override
	public void explode() {
		hurt(30);		
	}

	@Override
	public void explode(Actor a) {
		if(!invulnerable)
		{
			// Bounce back!
			xDirection = Math.cos(getAngleFromActor(a)) * -1;
			yDirection = Math.sin(getAngleFromActor(a));
			
			setAcceleration(3.75f);
		}
		explode();
	}
	
	public static Item getCurrentItem()
	{
		return getPlayer().currentItem;
	}
	
	public static float getBaseDamageResistance()
	{
		return getPlayer().damageResistance;
	}	
	
	public static float getDamageResistance()
	{		
		float inventoryBonus = getInventory().getDamageResistanceBonus();		
		float damageResistance = getBaseDamageResistance() + inventoryBonus;
		
		return damageResistance;
	}
	
	public static int getHealth()
	{
		return getPlayer().health; 
	}
	
	public static Inventory<Item> getInventory()
	{
		return getPlayer().inventory;
	}
	
	public static Inventory<Item> getInventory(int itemType)
	{
		Inventory<Item> z = new Inventory<Item>();		
		Item[] fullInventory = (Item[]) getPlayer().inventory.toArray(new Item[getPlayer().inventory.size()]);

		for(int i = 0; i < fullInventory.length; i++)
		{
			if(fullInventory[i].getItemType() == itemType)
			{
				z.add(fullInventory[i]);
			}
		}
		
		return z;
	}
	
	public static float getBaseLuck()
	{
		return getPlayer().luck;
	}
	
	public static boolean getInvulnerable()
	{
		return getPlayer().invulnerable;
	}
	
	public static float getLuck()
	{
		float inventoryBonus = getInventory().getLuckBonus();
		float luck = getBaseLuck() + inventoryBonus;
		
		return luck;
	}
	
	public static int getMaxHealth()
	{
		return getPlayer().maxHealth;
	}
	
	public static int getRuples()
	{
		return getPlayer().money;
	}

	public static float getBaseWiffaDamage()
	{
		return getPlayer().wiffaDamage;
	}
	
	public static float getWiffaDamage()
	{
		float inventoryBonus = getInventory().getWiffaDamageBonus();
		float wiffaDamage = getBaseWiffaDamage() + inventoryBonus;
		
		return wiffaDamage;
	}
	
	public float getAttackDamage()
	{	
		// Calculate damage based on base damage and acceleration!
		float damage =  Math.round(getWiffaDamage() * spiritAttackLevel);		
		// Damage cannot be less than 1... so return AT LEAST that.
		return Math.max(damage, 1);
	}
	
	public static Player getPlayer()
	{
		return playerInstance;
	}
	
	public double getXDirection()
	{
		return xDirection;
	}
	
	public static float getXPos()
	{
		return getPlayer().x;
	}

	public double getYDirection()
	{
		return yDirection;
	}
	
	public static float getYPos()
	{
		return getPlayer().y;
	}
	
	@Override
	public boolean move(double dirX, double dirY, Tile[][] tileMap)
	{					
		// Try to move the player if the game is unpaused and no dialog is raised.				
		boolean successfulMove = false;
		if(!(sfsoftware.game.TrevorLegacy.isPaused) && !(Dialog.displayDialog))
		{			
			xDirection = (int) dirX;
			yDirection = (int) dirY;
			
			// Change "direction" visually speaking
			if(xDirection > 0)
			{				
				setSrcY(80);
			}
			else if(xDirection < 0)
			{			
				setSrcY(120);
			}
			else if(yDirection > 0)
			{			
				setSrcY(0);
			}
			else if(yDirection < 0)
			{				
				setSrcY(40);
			}			
			
			// Set keyDown to true so animation occurs
			keyDown = true;
			
			// No clipping for testing / cheating.
			if(!(collidingWithCast(dirX, dirY)) && noClip)
			{
				super.moveWithoutCheckingCollision(dirX, dirY);
				return successfulMove;				
			}
			
			// Get the center coordinates of the player
			int x = (int) (this.getX() + (width / 2));
			int y = (int) (this.getY() + (height / 2));
			// Determine what tile the player is currently on
			int xTile = (int) Math.floor(((x - playingFieldX) / 40));
			int yTile = (int) Math.floor(((y - playingFieldY) / 40));													
			
			
			if(!(collidingWithCast(dirX, dirY)))
			{				
				updateCornerVariables(x, (int) (y + Math.round((speed * dirY))), tileMap);
				if(dirY < 0)
				{
					if(topLeft && topRight)
					{
						super.move(this.getX(), (float) (this.getY() + (dirY * speed)));
						successfulMove = true;
					}
					else
					{				
						super.move(this.getX(), playingFieldY + (yTile * 40));
					}
				}
				if(dirY > 0)
				{
					if(bottomLeft && bottomRight)
					{
						super.move(this.getX(), (float) (this.getY() + (dirY * speed)));
						successfulMove = true;
					}
					else
					{
						super.move(this.getX(), playingFieldY + (yTile * 40) + (40 - height));
					}			
				}
				updateCornerVariables((int) (x + Math.round((speed * dirX))), y, tileMap);		 
				if(dirX < 0)
				{
					if(topLeft && bottomLeft)
					{
						super.move((float) (this.getX() + (dirX * speed)), this.getY());
						successfulMove = true;
					}
					else
					{
						super.move(playingFieldX + (xTile * 40), this.getY());								
					}
				}
				if(dirX > 0)
				{
					if(topRight && bottomRight)
					{
						super.move((float) (this.getX() + (dirX * speed)), this.getY());
						successfulMove = true;
					}
					else
					{
						super.move(playingFieldX + (xTile * 40) + (40 - width), this.getY());
					}			
				}
			}
		}
		
		return successfulMove;
		
	}
	
	public static void heal(int health)
	{
		Player p = getPlayer();
		p.health = p.health + health;
		
		// Health cannot be greater than max health
		if(p.health > p.maxHealth)
		{
			p.health = p.maxHealth;
		}
	}
	
	public static void hurt(int h)
	{		
		// They can only get hurt if they are vulnerable!
		if(!(getInvulnerable()) && !(isUsingWiffa()))
		{
			Player p = getPlayer();
			
			// Account for damage resistance
			p.health = (int) ((float) p.health - (((float) h) / getPlayer().damageResistance));
			getPlayer().invulnerable = true;
			
			if((float)p.health / p.maxHealth < 0.33)
			{
				// If the player has less than 1/3 health remaining, play the more dramatic pain sound!
				Sound.playSound("trevhurt1");
			}
			else
			{
				// Play the usual pain sound.
				Sound.playSound("trevhurt0");
			}			
		}
	}
	
	private void incrementFrame()
	{
		setSrcX(getSrcX() + 40);
		if(getSrcX() > ((MAX_FRAMES * 40) - 40))
		{
			setSrcX(0);
		}		
	}
	
	public static boolean isUsingWiffa()
	{
		return getPlayer().usingWiffa;
	}
	
	public static void setLuck(float newValue)
	{
		getPlayer().luck = newValue;
	}
	
	public static void setMaxHealth(int newValue)
	{
		getPlayer().maxHealth = newValue;
	}
	
	/**
	 * Sets the noClip parameters to the provided value
	 * 
	 * @param b
	 */
	public void setNoClip(boolean b)
	{
		noClip = b;
	}
	
	public static void setRuples(int newValue)
	{
		if(newValue > getPlayer().walletSize)
		{
			getPlayer().money = getPlayer().walletSize;
		}
		else
		{
			getPlayer().money = newValue;
		}
	}
	
	public static boolean subtractFromRuples(int ruples)
	{
		if((getPlayer().money - ruples) < 0)
		{
			// This subtraction would put them in the negative - impossible!
			return false;
		}
		else
		{
			getPlayer().money = getPlayer().money - ruples;
			return true;
		}
	}

	public static void setCurrentItem(Item item)
	{
		getPlayer().currentItem = item;
	}	
	
	public static void setDamageResistance(float newValue)
	{
		getPlayer().damageResistance = newValue;
	}
	
	public static void setHealth(int newValue)
	{
		getPlayer().health = newValue;
	}
	
	@Override
	public void setPoisoned(boolean isPoisoned) {		
		poisoned = isPoisoned;
	}

	public static void setPlayerSpeed(int newSpeed)
	{
		getPlayer().setSpeed(newSpeed);
	}
	
	@Override
	protected void setSpeed(int newSpeed)
	{
		if(animationTimer != null && newSpeed > 2)
		{
			animationTimer.setInterval(150);
		}
		else if(animationTimer != null)
		{
			animationTimer.setInterval(180);
		}
		super.setSpeed(newSpeed);
	}
	
	public static void setWiffaDamage(float f)
	{
		getPlayer().wiffaDamage = f;
	}
	
	@Override
	public void togglePoisoned() {		
		setPoisoned(!poisoned);
	}
	
	@Override
	public void draw()
	{
		
		if(health > 0)
		{			
			if(!invulnerable)
			{
				super.draw();
			}
			else
			{
				// Blinky time :)
				if(invulnerabilityDraw)
				{
					super.draw();
				}
				invulnerabilityDraw = !invulnerabilityDraw;
			}
		}
		else
		{
			super.draw(width, height, this.getSrcX(), this.getSrcY(), width, height, 0, 0, 0, 1);
		}
		
		/*
		// Acceleration bar for debugging purposes...
		if(getAcceleration() > 0)
		{
			float barWidth = (((float)getAcceleration()) / 4.5f) * 40;
			Graphics.drawQuad(getX(), getY() - 10, 40, 6, 1, 1, 1, 1);
			Graphics.drawQuad(getX(), getY() - 10, barWidth , 6, 0, 0, 1, 1);
		}*/
		
	}
	
	public void update()
	{
		// Update the static variables with the x and y position of the Player instance
		x = this.getX();
		y = this.getY();
		
		if(animationTimer == null)
		{
			animationTimer = new Clock(180);
		}
		
		if(animationTimer.updateClock())
		{
			if(keyDown && !(TrevorLegacy.isPaused) && !(Dialog.displayDialog))
			{
				incrementFrame();
			}
			else if(getSrcX() % 80 != 0)
			{
				incrementFrame();
			}
		}
		
		if(invulnerabilityTimer.updateClock())
		{
			// The player can't stay invulnerable forever!
			if(invulnerable)
			{			
				invulnerabilityCoolDown++;
				// ~1500 ms;
				if(invulnerabilityCoolDown > 3)
				{
					invulnerable = false;
					invulnerabilityCoolDown = 0;
				}
			}
		}		

		// Accelerating!
		if(getAcceleration() != 0)
		{
			accelerate();
		}
		else if(isUsingWiffa())
		{
			usingWiffa = false;
			spiritAttackLevel = 1;
			wiffaAccel = 3.25f;
		}
		
		// Draw the player!
		draw();
	}

	@Override
	protected void updateCornerVariables(int x, int y, Tile[][] tileMap) 
	{
		// Override of Actor's method - this will prevent players from leaving the room if a boss fight is active.
		int upY = (int) Math.floor(((y - playingFieldY) - (height / 2)) / 40);
		int downY = (int) Math.floor((((y - playingFieldY) + (height / 2)) - 1) / 40);
		int leftX = (int) Math.floor(((x - playingFieldX)  - (width / 2)) / 40); 
		int rightX = (int) Math.floor((((x - playingFieldX)  + (width / 2)) - 1) / 40);
		
		//TODO Trying to move out of bounds during boss fights being strange when trying to move up or left
		try {
			topLeft = tileMap[leftX][upY].isWalkable();				
		}
		catch(ArrayIndexOutOfBoundsException e)
		{			
			topLeft = !(TrevorLegacy.bossFight);
		}
		try {
			bottomLeft = tileMap[leftX][downY].isWalkable();
		}
		catch(ArrayIndexOutOfBoundsException e)
		{			
			bottomLeft = !(TrevorLegacy.bossFight);
		}
		try {
			topRight = tileMap[rightX][upY].isWalkable();
		}
		catch(ArrayIndexOutOfBoundsException e)
		{			
			topRight = !(TrevorLegacy.bossFight);
		}
		try {		
			bottomRight = tileMap[rightX][downY].isWalkable();	
		}
		catch(ArrayIndexOutOfBoundsException e)
		{			
			bottomRight = !(TrevorLegacy.bossFight);
		}									
	}
	
	public void useCurrentItem()
	{
		// Use the current Item.
		currentItem.use(this);	
		
		// If it is a one-time use item, remove it from inventory.
		if(currentItem.isOneTimeUse())
		{
			inventory.remove(currentItem);
		}

	}
	
	public void useWiffa()
	{		
		int rand = (int) (Math.random() * 4);	
		String s = "trevwif" + rand;
		
		if(!usingWiffa)
		{	
			setAcceleration(wiffaAccel);
			usingWiffa = true;
			Sound.playSound(s);
			//TODO Wiffa animation!
		}		
		else if(spiritAttackLevel < spirit && getAcceleration() <= (wiffaAccel - 1.25f) && getAcceleration() >= (wiffaAccel - 2.75f))
		{					
			// Spirit attack!							
			wiffaAccel = wiffaAccel + 1;
			spiritAttackLevel++;			
			setAcceleration(wiffaAccel);
			Sound.playSound(s);
		}
	}
}			
